package com.googlecode.mavenfilesync;

/*
 * Copyright 2001-2005 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
import org.codehaus.plexus.util.xml.Xpp3DomWriter;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;

/**
 * Generates configuration files (.settings/de.loskutov.FileSync.prefs) and adds
 * FileSync project nature to the project (.project file). Does not refresh
 * project in eclipse workspace - a manual refresh is needed. If run from within
 * 
 * @goal generate
 * 
 * @phase initialize
 */
public class FileSyncSetupMojo extends AbstractMojo {

	private static final String SETTINGS_FILENAME = ".settings/de.loskutov.FileSync.prefs";
	private static final String BUILDER_NAME = "de.loskutov.FileSync.FSBuilder";

	/**
	 * Project base dir. Defaults to ${project.basedir}.
	 * 
	 * @parameter expression="${project.basedir}"
	 * @required
	 */
	File projectDirectory;

	/**
	 * Directory where files will be copied by default (if no specific location
	 * is given for a mapping).
	 * 
	 * @parameter default-value=""
	 */
	private final String defaultDestination;

	/**
	 * Path to variables substitution file, relative to the project directory.
	 * Contains variables in java properties format i.e. property=value.
	 * 
	 * @parameter expression="${generate.defaultVariables}" default-value=""
	 */
	private final String defaultVariables;

	/**
	 * If <code>true</code> then source management files (e.g. .svn directories)
	 * will be synchronized too. Default value is <code>false</code>.
	 * 
	 * @parameter expression="${generate.includeTeamPrivateFiles}"
	 *            default-value="false"
	 */
	private boolean includeTeamPrivateFiles;

	/**
	 * If <code>true</code> the destination files creation date will be set to
	 * the date when the synchronization took place.
	 * 
	 * @parameter default-value="false"
	 */
	private boolean useCurrentDateForDestinationFiles;

	/**
	 * Set override to <code>false</code> if the settings file should not be
	 * overriden.
	 * 
	 * @parameter expression="${generate.override}" default-value="true"
	 */
	private final boolean override;

	/**
	 * Set skip to <code>true</code> to skip execution
	 * 
	 * @parameter expression="${skip} default-value="false"
	 */
	private boolean skip;

	/**
	 * Multiple mappings can be defined - each one defines at least source
	 * directory (from there the files will be copied). If no defaultDirectory
	 * given, the mapping must also define the target directory (the destination
	 * where the files will be synchronized).
	 * 
	 * <pre>
	 * {@code
	 * 	<mappings>
	 * 		<mapping>
	 * 			<sourceFolder>source folder relative to project root</sourceFolder>
	 * 			<destinationFolder>System absolute path or workspace absolute path if preceded by :/</destinationFolder>
	 * 			<inclPatternList>
	 * 				<include>project root relative path or a path pattern in the Ant path notation</include>
	 * 				<include>another include</include>
	 * 			<include>C*</include>
	 * 			</inclPatternList>
	 * 			<exclPatternList>
	 * 				<exclude>project root relative path or a path pattern in the Ant path notation</exclude>
	 * 			</exclPatternList>
	 * 			<variablesFile>relative path to variables file</variablesFile>
	 * 		</mapping>
	 * 	</mappings>
	 * }
	 * </pre>
	 * 
	 * @see http://andrei.gmxhome.de/filesync/usage.html for details
	 * 
	 * @parameter expression="${generate.mappings}"
	 */
	private final FileSyncMapping[] mappings;

	public FileSyncSetupMojo() {
		defaultDestination = "";
		defaultVariables = "";
		mappings = new FileSyncMapping[0];
		projectDirectory = new File("");
		override = true;
	}

	public void execute() throws MojoExecutionException {
		if (skip) {
			getLog().info("Filesync generation skipped.");
		} else {
			if (addFilesyncBuildCommandToEclipseProject()) {
				generateSettings();
			}
		}
	}

	private boolean addFilesyncBuildCommandToEclipseProject()
			throws MojoExecutionException {

		final File projectFile = new File(projectDirectory, ".project");

		if (!projectFile.exists()) {
			getLog()
					.info(
							"No Eclipse .project file found. First import the maven project in Eclipse.");
			return false;
		}

		try {
			final Xpp3Dom dom = Xpp3DomBuilder
					.build(new FileReader(projectFile));

			final Xpp3Dom buildSpec = dom.getChild("buildSpec");

			if (buildSpec != null) {
				final Xpp3Dom[] buildCommands = buildSpec
						.getChildren("buildCommand");

				boolean hasFSBuildCommand = false;
				for (final Xpp3Dom buildCommand : buildCommands) {
					if (BUILDER_NAME.equals(buildCommand.getChild("name")
							.getValue())) {
						hasFSBuildCommand = true;
						break;
					}
				}

				if (!hasFSBuildCommand) {

					final Xpp3Dom buildCommand = new Xpp3Dom("buildCommand");
					final Xpp3Dom name = new Xpp3Dom("name");
					name.setValue(BUILDER_NAME);
					buildCommand.addChild(name);

					final Xpp3Dom arguments = new Xpp3Dom("arguments");
					arguments.setValue("");
					buildCommand.addChild(arguments);

					buildSpec.addChild(buildCommand);

					final FileWriter writer = new FileWriter(projectFile);
					Xpp3DomWriter.write(writer, dom);
					writer.close();

					getLog()
							.info(
									"The filesync build command has been added to the eclipse .project file.");
				} else {
					getLog()
							.info(
									"The .project file has not been changed. Filesync build command is already present in the eclipse .project file.");
				}
				return true;
			} else {
				throw new MojoExecutionException(
						"Malformed .project file: <buildSpec> section expected but not found.");
			}
		} catch (final FileNotFoundException e) {
			throw new MojoExecutionException(e.getMessage(), e);
		} catch (final XmlPullParserException e) {
			throw new MojoExecutionException(e.getMessage(), e);
		} catch (final IOException e) {
			throw new MojoExecutionException(e.getMessage(), e);
		}
	}

	private void generateSettings() throws MojoExecutionException {
		if (projectDirectory == null) {
			throw new IllegalStateException(
					"Unexpected state: project directory was supposed to be given by maven, but it is null.");
		}

		final String settingsFilename = projectDirectory + "/"
				+ SETTINGS_FILENAME;
		final File settingsFile = new File(settingsFilename);
		if (settingsFile.exists() && !override) {
			getLog()
					.info(
							"Skiping setting generation as settings file already exists and override set to false");
		} else {
			final SettingsBuilder settings = new SettingsBuilder();

			settings.with(Param.DEFAULT_DESTINATION, defaultDestination);
			settings.with(Param.DEFAULT_VARIABLES, defaultVariables);
			settings.with(Param.ECLIPSE_PREFERENCES_VERSION, "1");
			settings.with(Param.INCLUDE_TEAM_PRIVATE_FILES,
					includeTeamPrivateFiles);

			if (mappings != null) {
				for (final FileSyncMapping mapping : mappings) {
					settings.withMapping(mapping);
				}
			}

			settings.with(Param.USE_CURRENT_DATE_FOR_DESTINATION_FILES,
					useCurrentDateForDestinationFiles);
			FileWriter fileWriter = null;
			try {
				// ensure settings folder is available
				if (!settingsFile.getParentFile().exists()
						&& !settingsFile.getParentFile().mkdirs()) {
					throw new MojoExecutionException(
							"Cannot access or create Eclipse .settings folder ("
									+ settingsFile.getParent() + ")");
				}
				fileWriter = new FileWriter(settingsFile);
				fileWriter.write(settings.build());
				getLog().info(
						"The settings file generated: " + settingsFilename
								+ ".");
			} catch (final IOException e) {
				throw new MojoExecutionException(
						"Cannot write to settings file (" + settingsFilename
								+ "). ", e);
			} finally {
				if (fileWriter != null) {
					try {
						fileWriter.close();
					} catch (final IOException e) {
						throw new IllegalStateException(
								"Failed to close stream.", e);
					}
				}

			}
		}
	}

	/**
	 * Defines all the possible params that can be used in the settings file -
	 * defined by the filesync settings file standard.
	 * 
	 * @author marek
	 */
	public static enum Param {
		// CODEFORMATTER:OFF
		DEFAULT_DESTINATION("defaultDestination"), //
		DEFAULT_VARIABLES("defaultVariables"), //
		ECLIPSE_PREFERENCES_VERSION("eclipse.preferences.version"), //
		INCLUDE_TEAM_PRIVATE_FILES("includeTeamPrivateFiles"), //
		USE_CURRENT_DATE_FOR_DESTINATION_FILES(
				"useCurrentDateForDestinationFiles"); //
		// CODEFORMATTER:ON

		private final String name;

		private Param(final String name) {
			this.name = name;
		}

		@Override
		public String toString() {
			return this.name;
		}
	}
}
